package com.my.core.util.map;

import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * 地图工具类（完整版）
 * 支持多坐标系（百度BD09、谷歌WGS84、天地图GCJ02/CGCS2000）的：
 * 1. 点面判断、点圆判断
 * 2. 两点距离计算（米）
 * 3. 多边形面积计算（平方米/平方公里/亩）
 * 4. 坐标系转换（BD09↔GCJ02、GCJ02→WGS84）
 * 5. 单位转换（平方米↔平方公里↔亩）
 */
public class MapUtils {
    // --------------------- 核心常量定义 ---------------------
    // WGS84/CGCS2000地球半径（米）：二者椭球长半轴完全一致，计算通用
    private static final double EARTH_RADIUS = 6378137.0;
    // 角度转弧度系数
    private static final double RADIAN_CONVERSION = Math.PI / 180.0;
    // 百度坐标转换专用常量（X_PI）
    public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0;
    // 单位转换常量
    private static final double SQUARE_METERS_PER_SQUARE_KILOMETER = 1_000_000; // 1平方公里=1e6平方米
    private static final double SQUARE_METERS_PER_MU = 666.6667; // 1亩≈666.6667平方米

    // --------------------- 经纬度点模型 ---------------------
    /**
     * 经纬度点坐标类
     * 存储经度（longitude）和纬度（latitude）
     */
    public static class Point {
        private double longitude; // 经度（x轴）
        private double latitude;  // 纬度（y轴）

        public Point(double longitude, double latitude) {
            this.longitude = longitude;
            this.latitude = latitude;
        }

        public double getLongitude() {
            return longitude;
        }

        public double getLatitude() {
            return latitude;
        }

        @Override
        public String toString() {
            return "Point{lon=" + longitude + ", lat=" + latitude + '}';
        }
    }

    // --------------------- 坐标系转换核心方法 ---------------------
    /**
     * 1. GCJ02坐标系（天地图Web端、高德地图）转WGS84坐标系（谷歌国际版、计算基准）
     * @param gcjLon GCJ02经度
     * @param gcjLat GCJ02纬度
     * @return WGS84坐标点
     */
    public static Point gcj02ToWgs84(double gcjLon, double gcjLat) {
        // 国内坐标需偏移校正，国外坐标直接返回（GCJ02仅覆盖中国境内）
        if (outOfChina(gcjLat, gcjLon)) {
            return new Point(gcjLon, gcjLat);
        }
        // 计算GCJ02相对于WGS84的偏移量
        double[] delta = calculateDelta(gcjLon, gcjLat);
        // WGS84坐标 = GCJ02坐标 - 偏移量
        return new Point(gcjLon - delta[0], gcjLat - delta[1]);
    }

    /**
     * 2. 百度BD09坐标系转GCJ02坐标系（高德/天地图基础）
     * @param bdLon BD09经度
     * @param bdLat BD09纬度
     * @return 包含GCJ02经纬度的Map（key: "lon"->"lat"）
     */
    public static Map<String, Double> bd2gd(double bdLon, double bdLat) {
        Map<String, Double> result = new HashMap<>(2);
        double x = bdLon - 0.0065; // 百度经度偏移校正
        double y = bdLat - 0.006;  // 百度纬度偏移校正
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
        // 计算GCJ02坐标
        result.put("lon", z * Math.cos(theta));
        result.put("lat", z * Math.sin(theta));
        return result;
    }

    /**
     * 3. 高德GCJ02坐标系转百度BD09坐标系
     * @param gdLon GCJ02经度
     * @param gdLat GCJ02纬度
     * @return 包含BD09经纬度的Map（key: "lon"->"lat"）
     */
    public static Map<String, Double> gd2bd(double gdLon, double gdLat) {
        Map<String, Double> result = new HashMap<>(2);
        double x = gdLon;
        double y = gdLat;
        double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * X_PI);
        double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * X_PI);
        // 计算BD09坐标
        result.put("lon", z * Math.cos(theta) + 0.0065);
        result.put("lat", z * Math.sin(theta) + 0.006);
        return result;
    }

    /**
     * 辅助方法：判断坐标是否在中国大陆境内（GCJ02加密范围）
     */
    private static boolean outOfChina(double lat, double lon) {
        return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
    }

    /**
     * 辅助方法：计算GCJ02相对于WGS84的经纬度偏移量
     */
    private static double[] calculateDelta(double lon, double lat) {
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLng(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radLat);
        // 椭球扁率相关参数（WGS84/CGCS2000通用）
        magic = 1 - 0.006693421622965943 * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        // 校正纬度偏移（弧度转角度）
        dLat = (dLat * 180.0) / ((EARTH_RADIUS * (1 - 0.006693421622965943)) / (magic * sqrtMagic) * Math.PI);
        // 校正经度偏移（弧度转角度）
        dLon = (dLon * 180.0) / (EARTH_RADIUS / sqrtMagic * Math.cos(radLat) * Math.PI);
        return new double[]{dLon, dLat}; // 返回[经度偏移, 纬度偏移]
    }

    /**
     * 辅助方法：纬度偏移量计算（GCJ02加密算法核心）
     */
    private static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 辅助方法：经度偏移量计算（GCJ02加密算法核心）
     */
    private static double transformLng(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    }

    // --------------------- 空间判断方法 ---------------------
    /**
     * 判断点是否在多边形区域内（支持任意闭合多边形）
     * @param point 待判断点
     * @param polygon 多边形顶点列表（需按顺时针/逆时针顺序排列）
     * @return true=在区域内，false=不在
     */
    public static boolean isInPolygon(Point point, List<Point> polygon) {
        if (point == null || polygon == null || polygon.size() < 3) {
            return false;
        }
        GeneralPath path = new GeneralPath();
        // 初始化路径：移动到第一个顶点
        Point first = polygon.get(0);
        path.moveTo(first.getLongitude(), first.getLatitude());
        // 连接后续顶点
        for (int i = 1; i < polygon.size(); i++) {
            Point p = polygon.get(i);
            path.lineTo(p.getLongitude(), p.getLatitude());
        }
        // 闭合路径
        path.closePath();
        // 判断点是否在路径内
        return path.contains(point.getLongitude(), point.getLatitude());
    }

    /**
     * 重载：通过经纬度数组判断点是否在多边形内
     * @param pointLon 待判断点经度
     * @param pointLat 待判断点纬度
     * @param lon 多边形顶点经度数组
     * @param lat 多边形顶点纬度数组
     * @return true=在区域内，false=不在
     */
    public static boolean isInPolygon(double pointLon, double pointLat, Double[] lon, Double[] lat) {
        if (lon == null || lat == null || lon.length != lat.length || lon.length < 3) {
            return false;
        }
        // 转换为Point列表
        List<Point> polygon = new ArrayList<>(lon.length);
        for (int i = 0; i < lon.length; i++) {
            polygon.add(new Point(lon[i], lat[i]));
        }
        return isInPolygon(new Point(pointLon, pointLat), polygon);
    }

    /**
     * 判断点是否在圆形区域内（按地球球面距离计算）
     * @param point 待判断点
     * @param center 圆心点
     * @param radius 半径（单位：米）
     * @return true=在圆内，false=不在
     */
    public static boolean isInCircle(Point point, Point center, double radius) {
        if (point == null || center == null || radius < 0) {
            return false;
        }
        // 计算两点球面距离，对比半径
        double distance = getDistance(point, center);
        return distance <= radius;
    }

    /**
     * 重载：通过经纬度判断点是否在圆形内
     * @param lng1 待判断点经度
     * @param lat1 待判断点纬度
     * @param lng2 圆心经度
     * @param lat2 圆心纬度
     * @param radius 半径（米）
     * @return true=在圆内，false=不在
     */
    public static boolean isInCircle(double lng1, double lat1, double lng2, double lat2, double radius) {
        if (radius < 0) {
            return false;
        }
        double distance = getDistance(lat1, lng1, lat2, lng2);
        return distance <= radius;
    }

    // --------------------- 距离与面积计算 ---------------------
    /**
     * 计算两点之间的球面距离（单位：米）
     * @param point1 第一个点
     * @param point2 第二个点
     * @return 两点距离（保留3位小数）
     */
    public static double getDistance(Point point1, Point point2) {
        if (point1 == null || point2 == null) {
            return 0.0;
        }
        return getDistance(
                point1.getLatitude(), point1.getLongitude(),
                point2.getLatitude(), point2.getLongitude()
        );
    }

    /**
     * 重载：通过经纬度计算两点球面距离（单位：米）
     * 算法：Haversine公式（球面距离通用算法）
     */
    public static double getDistance(double lat1, double lng1, double lat2, double lng2) {
        // 角度转弧度
        double radLat1 = toRadians(lat1);
        double radLat2 = toRadians(lat2);
        double radLng1 = toRadians(lng1);
        double radLng2 = toRadians(lng2);

        // Haversine公式核心计算
        double deltaLat = radLat2 - radLat1;
        double deltaLng = radLng2 - radLng1;
        double a = Math.pow(Math.sin(deltaLat / 2), 2)
                + Math.cos(radLat1) * Math.cos(radLat2)
                * Math.pow(Math.sin(deltaLng / 2), 2);
        double c = 2 * Math.asin(Math.sqrt(a));

        // 计算距离（米），保留3位小数
        double distance = c * EARTH_RADIUS;
        return Math.round(distance * 1000) / 1000.0;
    }

    /**
     * 计算多边形区域的球面面积（单位：平方米）
     * 算法：球面多边形面积公式（基于WGS84/CGCS2000椭球）
     * @param polygon 多边形顶点列表（需按顺时针/逆时针顺序，闭合或不闭合均可）
     * @return 面积（平方米），点数量不足3个时返回0
     */
    public static double calculateAreaInSquareMeters(List<Point> polygon) {
        if (polygon == null || polygon.size() < 3) {
            return 0.0;
        }

        double area = 0.0;
        int n = polygon.size();
        for (int i = 0; i < n; i++) {
            // 取当前点和下一个点（最后一个点的下一个点是第一个点）
            Point p1 = polygon.get(i);
            Point p2 = polygon.get((i + 1) % n);

            // 经纬度转弧度
            double lon1 = toRadians(p1.getLongitude());
            double lat1 = toRadians(p1.getLatitude());
            double lon2 = toRadians(p2.getLongitude());
            double lat2 = toRadians(p2.getLatitude());

            // 累加相邻点的面积贡献
            double deltaLon = lon2 - lon1;
            area += deltaLon * (2 + Math.sin(lat1) + Math.sin(lat2));
        }

        // 计算最终面积（取绝对值，避免顺序影响符号）
        area = Math.abs(area * EARTH_RADIUS * EARTH_RADIUS / 2.0);
        return area;
    }

    /**
     * 计算多边形面积（单位：平方公里）
     */
    public static double calculateAreaInSquareKilometers(List<Point> polygon) {
        return squareMetersToSquareKilometers(calculateAreaInSquareMeters(polygon));
    }

    /**
     * 计算多边形面积（单位：亩）
     */
    public static double calculateAreaInMu(List<Point> polygon) {
        return squareMetersToMu(calculateAreaInSquareMeters(polygon));
    }

    /**
     * 重载：通过经纬度数组计算面积（单位：平方公里，返回BigDecimal精度控制）
     */
    public static BigDecimal getArea(Double[] lon, Double[] lat) {
        if (lon == null || lat == null || lon.length != lat.length) {
            return BigDecimal.ZERO;
        }
        List<Point> polygon = new ArrayList<>(lon.length);
        for (int i = 0; i < lon.length; i++) {
            polygon.add(new Point(lon[i], lat[i]));
        }
        double areaSqkm = calculateAreaInSquareKilometers(polygon);
        // 保留6位小数，四舍五入
        return BigDecimal.valueOf(areaSqkm).setScale(6, RoundingMode.HALF_UP);
    }

    // --------------------- 单位转换方法 ---------------------
    /**
     * 平方米转平方公里
     */
    public static double squareMetersToSquareKilometers(double squareMeters) {
        return squareMeters / SQUARE_METERS_PER_SQUARE_KILOMETER;
    }

    /**
     * 平方公里转平方米
     */
    public static double squareKilometersToSquareMeters(double squareKilometers) {
        return squareKilometers * SQUARE_METERS_PER_SQUARE_KILOMETER;
    }

    /**
     * 平方米转亩
     */
    public static double squareMetersToMu(double squareMeters) {
        return squareMeters / SQUARE_METERS_PER_MU;
    }

    /**
     * 亩转平方米
     */
    public static double muToSquareMeters(double mu) {
        return mu * SQUARE_METERS_PER_MU;
    }

    /**
     * 亩转平方公里
     */
    public static double muToSquareKilometers(double mu) {
        return squareMetersToSquareKilometers(muToSquareMeters(mu));
    }

    /**
     * 平方公里转亩
     */
    public static double squareKilometersToMu(double squareKilometers) {
        return squareMetersToMu(squareKilometersToSquareMeters(squareKilometers));
    }

    // --------------------- 工具辅助方法 ---------------------
    /**
     * 角度转弧度（内部通用）
     */
    private static double toRadians(double degree) {
        return degree * RADIAN_CONVERSION;
    }

    // --------------------- 测试方法（验证多坐标系兼容性） ---------------------
    public static void main(String[] args) {
        System.out.println("=== 1. 谷歌地图（WGS84）测试：直接计算 ===");
        // 谷歌WGS84坐标（近似1000平方米正方形）
        List<Point> googleWgs84Points = Arrays.asList(
                new Point(116.403874, 39.914885),
                new Point(116.404974, 39.914885),
                new Point(116.404974, 39.913985),
                new Point(116.403874, 39.913985)
        );
        double googleAreaSqm = calculateAreaInSquareMeters(googleWgs84Points);
        System.out.println("谷歌WGS84面积：" + String.format("%.2f", googleAreaSqm) + " 平方米");
        System.out.println("谷歌WGS84面积：" + String.format("%.4f", calculateAreaInMu(googleWgs84Points)) + " 亩");

        System.out.println("\n=== 2. 百度地图（BD09）测试：BD09→GCJ02→WGS84 ===");
        // 百度BD09坐标（对应上述谷歌区域的百度加密坐标）
        Point bdPoint1 = new Point(116.410412, 39.921429);
        Point bdPoint2 = new Point(116.411512, 39.921429);
        Point bdPoint3 = new Point(116.411512, 39.920529);
        Point bdPoint4 = new Point(116.410412, 39.920529);
        // 步骤1：BD09→GCJ02
        Map<String, Double> gd1 = bd2gd(bdPoint1.getLongitude(), bdPoint1.getLatitude());
        Map<String, Double> gd2 = bd2gd(bdPoint2.getLongitude(), bdPoint2.getLatitude());
        Map<String, Double> gd3 = bd2gd(bdPoint3.getLongitude(), bdPoint3.getLatitude());
        Map<String, Double> gd4 = bd2gd(bdPoint4.getLongitude(), bdPoint4.getLatitude());
        // 步骤2：GCJ02→WGS84
        Point bdWgs84Point1 = gcj02ToWgs84(gd1.get("lon"), gd1.get("lat"));
        Point bdWgs84Point2 = gcj02ToWgs84(gd2.get("lon"), gd2.get("lat"));
        Point bdWgs84Point3 = gcj02ToWgs84(gd3.get("lon"), gd3.get("lat"));
        Point bdWgs84Point4 = gcj02ToWgs84(gd4.get("lon"), gd4.get("lat"));
        // 计算面积
        List<Point> bdWgs84Points = Arrays.asList(bdWgs84Point1, bdWgs84Point2, bdWgs84Point3, bdWgs84Point4);
        double bdAreaSqm = calculateAreaInSquareMeters(bdWgs84Points);
        System.out.println("百度BD09转WGS84后面积：" + String.format("%.2f", bdAreaSqm) + " 平方米");

        System.out.println("\n=== 3. 天地图测试：GCJ02→WGS84（天地图Web端） ===");
        // 天地图GCJ02坐标（对应上述区域的天地图坐标）
        Point tdtGcjPoint1 = new Point(116.409914, 39.918852);
        Point tdtGcjPoint2 = new Point(116.411014, 39.918852);
        Point tdtGcjPoint3 = new Point(116.411014, 39.917952);
        Point tdtGcjPoint4 = new Point(116.409914, 39.917952);
        // 步骤：GCJ02→WGS84
        Point tdtWgs84Point1 = gcj02ToWgs84(tdtGcjPoint1.getLongitude(), tdtGcjPoint1.getLatitude());
        Point tdtWgs84Point2 = gcj02ToWgs84(tdtGcjPoint2.getLongitude(), tdtGcjPoint2.getLatitude());
        Point tdtWgs84Point3 = gcj02ToWgs84(tdtGcjPoint3.getLongitude(), tdtGcjPoint3.getLatitude());
        Point tdtWgs84Point4 = gcj02ToWgs84(tdtGcjPoint4.getLongitude(), tdtGcjPoint4.getLatitude());
        // 计算面积
        List<Point> tdtWgs84Points = Arrays.asList(tdtWgs84Point1, tdtWgs84Point2, tdtWgs84Point3, tdtWgs84Point4);
        double tdtAreaSqm = calculateAreaInSquareMeters(tdtWgs84Points);
        System.out.println("天地图GCJ02转WGS84后面积：" + String.format("%.2f", tdtAreaSqm) + " 平方米");

        System.out.println("\n=== 4. 两点距离测试（百度BD09坐标） ===");
        double distance = getDistance(bdPoint1, bdPoint2);
        System.out.println("百度BD09两点距离：" + String.format("%.2f", distance) + " 米（理论值：111米）");
    }
}